﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using LowLevelGraphics.Filter;

using LowLevelGraphics;
using LowLevelGraphics.Histograms;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Grayscale Modes...
    /// </summary>
    public enum GRAYSCALE
    {
        GRAYSCALE1,
        GRAYSCALE2,
        GRAYSCALE_MEDIAN,
        GRAYSCALE_MEAN,
        GRAYSCALE_MINCHANNEL,
        GRAYSCALE_MAXCHANNEL,
        GRAYSCALE_SATURATION,
        GRAYSCALE_BRIGHTNESS,
        GRAYSCALE_RED_CHANNEL,
        GRAYSCALE_GREEN_CHANNEL,
        GRAYSCALE_BLUE_CHANNEL,
        DEFAULT,
        ENHANCED_LIGHTNESS_MODEL,
        GRAYSCALE_HUE,
        CUSTOM,        
        RED_FILTER,
        GREEN_FILTER,
        BLUE_FILTER,
        ORANGE_FILTER,
        YELLOW_FILTER,
        INFRARE, 
        BT709,
        RMY,
        Y
    }

    /// <summary>
    /// Class to be able to convert any rgb image to different graystages.
    /// ENHANCED_LIGZHTNESS_MODE uses a formula which should result in improved luminances i.e. should be besser than the
    /// normal default gray calculation.
    /// </summary>
    public class GrayScale : BaseImageFilter
    {
        protected delegate double GrayScaleCalc(int _nR, int _nG, int _nB);
        protected GRAYSCALE M_GRAYSCALE = GRAYSCALE.DEFAULT;
        protected Dictionary<GRAYSCALE, Delegate> m_aMode = new Dictionary<GRAYSCALE, Delegate>();

        /// <summary>
        /// Initializes a new instance of the <see cref="GrayScale"/> class.
        /// </summary>
        public GrayScale()
            : this(GRAYSCALE.DEFAULT)
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GrayScale"/> class.
        /// </summary>
        /// <param name="_nRedFactor">The _n red factor.</param>
        /// <param name="_nGreenFactor">The _n green factor.</param>
        /// <param name="_nBlueFactor">The _n blue factor.</param>
        public GrayScale(int _nRedFactor, int _nGreenFactor, int _nBlueFactor)
            : this(_nRedFactor, _nGreenFactor, _nBlueFactor, 256)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GrayScale"/> class.
        /// </summary>
        /// <param name="_nRedFactor">The _n red factor.</param>
        /// <param name="_nGreenFactor">The _n green factor.</param>
        /// <param name="_nBlueFactor">The _n blue factor.</param>
        /// <param name="_nDivisor">The _n divisor.</param>
        public GrayScale(int _nRedFactor, int _nGreenFactor, int _nBlueFactor, int _nDivisor)
        {
            this.M_GRAYSCALE = GRAYSCALE.CUSTOM;
            this.m_nRedFactor = _nRedFactor; 
            this.m_nGreenFactor = _nGreenFactor;
            this.m_nBlueFactor = _nBlueFactor;
            this.m_nDivisor = _nDivisor;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GrayScale"/> class.
        /// </summary>
        /// <param name="_GRAYSCALE">The _ GRAYSCALE.</param>
        public GrayScale(GRAYSCALE _GRAYSCALE)
        {
            M_GRAYSCALE = _GRAYSCALE;
            
            m_aMode.Add(GRAYSCALE.DEFAULT, new GrayScaleCalc(GrayScale1));
            m_aMode.Add(GRAYSCALE.GRAYSCALE1, new GrayScaleCalc(GrayScale1));
            m_aMode.Add(GRAYSCALE.GRAYSCALE2, new GrayScaleCalc(GrayScale2));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_MEDIAN, new GrayScaleCalc(GrayScaleMedian));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_MEAN, new GrayScaleCalc(GrayScaleMean));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_MINCHANNEL, new GrayScaleCalc(GrayScaleMinChannel));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_MAXCHANNEL, new GrayScaleCalc(GrayScaleMaxChannel));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_BRIGHTNESS, new GrayScaleCalc(GrayScaleBrightness));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_SATURATION, new GrayScaleCalc(GrayScaleSaturation));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_RED_CHANNEL, new GrayScaleCalc(GrayScaleRedChannel));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_GREEN_CHANNEL, new GrayScaleCalc(GrayScaleGreenChannel));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_BLUE_CHANNEL, new GrayScaleCalc(GrayScaleBlueChannel));
            m_aMode.Add(GRAYSCALE.GRAYSCALE_HUE, new GrayScaleCalc(GrayScaleHue));
            m_aMode.Add(GRAYSCALE.CUSTOM, new GrayScaleCalc(GrayScaleCustom));
            m_aMode.Add(GRAYSCALE.ORANGE_FILTER, new GrayScaleCalc(GrayScaleOrangeFilter));
            m_aMode.Add(GRAYSCALE.RED_FILTER, new GrayScaleCalc(GrayScaleRedFilter));
            m_aMode.Add(GRAYSCALE.BLUE_FILTER, new GrayScaleCalc(GrayScaleBlueFilter));
            m_aMode.Add(GRAYSCALE.GREEN_FILTER, new GrayScaleCalc(GrayScaleGreenFilter));
            m_aMode.Add(GRAYSCALE.YELLOW_FILTER, new GrayScaleCalc(GrayScaleYellowFilter));
            m_aMode.Add(GRAYSCALE.INFRARE, new GrayScaleCalc(GrayScaleInfrareFilter));
            m_aMode.Add(GRAYSCALE.BT709, new GrayScaleCalc(BT709));
            m_aMode.Add(GRAYSCALE.Y, new GrayScaleCalc(GrayScale1));
        }

        /// <summary>
        /// GrayScale
        /// </summary>
        /// <param name="_grayScale"></param>
        internal GrayScale(GrayScale _grayScale)
        {
            M_GRAYSCALE = _grayScale.M_GRAYSCALE;
        }

        /// <summary>
        /// Gets or sets the gray scale mode.
        /// </summary>
        /// <value>The gray scale mode.</value>
        public GRAYSCALE GrayScaleMode
        {
            get { return M_GRAYSCALE; }
            set
            { 
                M_GRAYSCALE = value;
            }
        }

        /// <summary>
        /// Gets or sets the red factor.
        /// </summary>
        /// <value>The red factor.</value>
        public int RedFactor
        {
            get { return m_nRedFactor; }
            set { m_nRedFactor = value; }
        }

        /// <summary>
        /// Gets or sets the green factor.
        /// </summary>
        /// <value>The green factor.</value>
        public int GreenFactor
        {
            get { return m_nGreenFactor; }
            set { m_nGreenFactor = value; }
        }

        /// <summary>
        /// Gets or sets the blue factor.
        /// </summary>
        /// <value>The blue factor.</value>
        public int BlueFactor
        {
            get { return m_nBlueFactor; }
            set { m_nBlueFactor = value; }
        }

        /// <summary>
        /// Gets or sets the division.
        /// </summary>
        /// <value>The division.</value>
        public int Divisor
        {
            get { return m_nDivisor; }
            set { m_nDivisor = value; }
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            ConvertToGrayScale(_bitmap);
            return _bitmap;
        }

        /// <summary>
        /// Converts given bitmap to grayscale.
        /// </summary>
        /// <param name="b">The b.</param>
        private void ConvertToGrayScale(UnsafeBitmap b)
        {
            if (M_GRAYSCALE == GRAYSCALE.ENHANCED_LIGHTNESS_MODEL)
            {
                EnhancedLightnessModel(b);
                return;
            }

            GrayScaleConvert(b);
        }

        /// <summary>
        /// Grays the scale convert.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        private void GrayScaleConvert(UnsafeBitmap _bitmap)
        {
            //Fetch the delegate we need for the graylevel conversion
            GrayScaleCalc grayScale = (GrayScaleCalc)m_aMode[M_GRAYSCALE];

            // GDI+ still lies to us - the return format is BGR, NOT RGB.
            BitmapData bmData = _bitmap.BitmapData; //b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            int stride = bmData.Stride;
            IntPtr Scan0 = bmData.Scan0;


            unsafe
            {
                byte* p = (byte*)(void*)Scan0;

                int nOffset = _bitmap.OffsetPointerIncrease;
                int nByteCount = _bitmap.BytesPerPixelCount;

                byte red, green, blue;

                for (int y = 0; y < _bitmap.Height; ++y)
                {
                    for (int x = 0; x < _bitmap.Width; ++x)
                    {
                        blue = p[0];
                        green = p[1];
                        red = p[2];

                        p[0] = p[1] = p[2] = (byte)(grayScale(red, green, blue));

                        p += nByteCount;
                    }
                    p += nOffset;
                }
            }
        }

        /// <summary>
        /// Enhanceds the lightness model.
        /// </summary>
        /// <param name="b">The b.</param>
        private static void EnhancedLightnessModel(UnsafeBitmap b)
        {
            Statistics histogramm = new Statistics(b);
            histogramm.SortBy(PROCESSING_MODE.Luminance);
            UnsafeBitmap bitmap = b;

            float fMinLuminance = histogramm[0].Luminance;
            float fMaxLuminance = histogramm[histogramm.Count - 1].Luminance;

            int nHeight = bitmap.Height;
            int nWidth = bitmap.Width;

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    float fLightness = fMinLuminance + (fMaxLuminance - fMinLuminance) * bitmap.GetPixel(x, y).GetBrightness();
                    int nValue = (int)fLightness;
                    bitmap.SetPixel(x, y, Color.FromArgb(nValue, nValue, nValue));
                }
            }
            return;
        }

        /// <summary>
        /// This is a default conversion
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScale1(int _nRed, int _nGreen, int _nBlue)
        {
            return (0.299 * _nRed + 0.587 * _nGreen + 0.114 * _nBlue);
        }

        /// <summary>
        /// This is also a default conversion using slightly different values
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScale2(int _nRed, int _nGreen, int _nBlue)
        {
            return (0.3 * _nRed + 0.59 * _nRed + 0.11 * _nBlue);
        }

        /// <summary>
        /// Grays the scale mean.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleMean(int _nRed, int _nGreen, int _nBlue)
        {
            return (_nRed + _nGreen + _nBlue) / 3;
        }

        /// <summary>
        /// Grays the scale median.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleMedian(int _nRed, int _nGreen, int _nBlue)
        {
            return ColorFunctions.Sort(Color.FromArgb(_nRed, _nGreen, _nBlue), true).G;
        }

        /// <summary>
        /// Grays the scale max channel.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleMaxChannel(int _nRed, int _nGreen, int _nBlue)
        {
            return Math.Max(_nRed, Math.Max(_nGreen, _nBlue));
        }

        /// <summary>
        /// Grays the scale min channel.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleMinChannel(int _nRed, int _nGreen, int _nBlue)
        {
            return Math.Min(_nRed, Math.Min(_nGreen, _nBlue));
        }

        /// <summary>
        /// Grays the scale brightness.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleBrightness(int _nRed, int _nGreen, int _nBlue)
        {
            return Color.FromArgb(_nRed, _nGreen, _nBlue).GetBrightness() * 255.0f;
        }

        /// <summary>
        /// Grays the scale saturation.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleSaturation(int _nRed, int _nGreen, int _nBlue)
        {
            return Color.FromArgb(_nRed, _nGreen, _nBlue).GetSaturation() * 255.0f;
        }

        /// <summary>
        /// Gets the grayscale red channel
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>

        private double GrayScaleRedChannel(int _nRed, int _nGreen, int _nBlue)
        {
            return _nRed;
        }

        /// <summary>
        /// Gets the grayscale green channel
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleGreenChannel(int _nRed, int _nGreen, int _nBlue)
        {
            return _nGreen;
        }

        /// <summary>
        /// Gets the grayscale blue channel.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleBlueChannel(int _nRed, int _nGreen, int _nBlue)
        {
            return _nBlue;
        }

        /// <summary>
        /// 
        /// </summary>
        private const double m_dHueFactor = 255.0f / 360.0f;

        /// <summary>
        /// returns the gray scale hue.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleHue(int _nRed, int _nGreen, int _nBlue)
        {
            return Color.FromArgb(_nRed, _nGreen, _nBlue).GetHue() * m_dHueFactor;
        }

        protected int m_nRedFactor = 77;
        protected int m_nGreenFactor = 151;
        protected int m_nBlueFactor = 28;
        protected int m_nDivisor = 256;

        /// <summary>
        /// Grays the scale custom.
        /// </summary>
        /// <param name="_nRed">The _n red.</param>
        /// <param name="_nGreen">The _n green.</param>
        /// <param name="_nBlue">The _n blue.</param>
        /// <returns></returns>
        private double GrayScaleCustom(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor)/m_nDivisor;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleOrangeFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor) / m_nDivisor;
        }

        /// <summary>
        /// Red Filter
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleRedFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor) / m_nDivisor;
        }

        /// <summary>
        /// Blue Filter
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleBlueFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor) / m_nDivisor;
        }

        /// <summary>
        /// Yellow Filter
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleYellowFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor) / m_nDivisor;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleGreenFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor + _nGreen * m_nGreenFactor + _nBlue * m_nBlueFactor) / m_nDivisor;
        }

        /// <summary>
        ///  percentage values (-70%, +200%, -30%)
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double GrayScaleInfrareFilter(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * m_nRedFactor * -0.7 + _nGreen * m_nGreenFactor * 2 + _nBlue * m_nBlueFactor * -0.3) / m_nDivisor;
        }

        /// <summary>
        /// r 0.2125, g 0.7154, b 0.0721
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double BT709(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * 0.2125 + _nGreen * 0.7154 + _nBlue * 0.0721);
        } 
        
        /// <summary>
        /// r 0.5, g 0.419, b 0.081
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double RMY(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * 0.5 + _nGreen * 0.419 + _nBlue * 0.081);
        }
        
        /// <summary>
        /// r 0.299, g 0.587, b 0.114  
        /// </summary>
        /// <param name="_nRed"></param>
        /// <param name="_nGreen"></param>
        /// <param name="_nBlue"></param>
        /// <returns></returns>
        private double Y(int _nRed, int _nGreen, int _nBlue)
        {
            return (double)(_nRed * 0.5 + _nGreen * 0.419 + _nBlue * 0.081);
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new GrayScale(this);
        }
    }
}
